/**
 * Copyright © 2012, 2013 dr. ir. Jeroen M. Valk
 * 
 * This file is part of ComPosiX.
 * 
 * ComPosiX is free software: you can redistribute it and/or modify it under the
 * terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 * 
 * ComPosiX is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * ComPosiX. If not, see <http://www.gnu.org/licenses/>.
 */

var Root = (function() {
	var util = require('util');
	var fs = require('fs');
	var evals = process.binding('evals');
	var context = {};

	context.Logger = Logger;

	include('lib/fifo.js', context);
	include('lib/superior.js', context);
	include('lib/subordinate.js', context);

	var Superior = context.Superior;
	var Subordinate = context.Subordinate;

	function assert(condition) {
		if (!condition) {
			throw new Error('assertion failed');
		}
	}

	function include(filename, sandbox) {
		process.stderr.write("Loading '" + filename + "'...");
		var content = null;
		try {
			content = fs.readFileSync(filename, 'utf8');
		} catch (e) {
			process.stderr.write("NOT FOUND\n");
		}
		if (content != null) {
			evals.NodeScript.runInNewContext(content, sandbox, filename);
			process.stderr.write("OK\n");
		}
	}

	function assign(value, name) {
		var result = {};
		for ( var i = 0; i < name.length; ++i) {
			result[name[i]] = value[i];
		}
		return result;
	}

	function cons(cardinal, func) {
		var result = [];
		if (typeof cardinal == 'number') {
			for ( var i = 0; i < cardinal; ++i) {
				result[i] = func(i);
			}
		} else {
			if (typeof cardinal == 'object'
					&& typeof cardinal.length == 'number') {
				for ( var i in cardinal) {
					result[i] = func(cardinal[i]);
				}
			} else {
				throw 'cons(cardinal, func): tuple comprehension requires number or array as cardinal';
			}
		}
		return result;
	}

	function Logger(name) {
		var self = this;

		var trace, debug, info;

		self.level = 'warn';

		function setLevel() {
			trace = false;
			debug = false;
			info = false;
			switch (self.level) {
			case 'trace':
				trace = true;
			case 'debug':
				debug = true;
			case 'info':
				info = true;
			}
		}

		this.trace = function(msg) {
			setLevel();
			if (trace) {
				if (typeof (msg) == "string") {
					util.debug('TRACE: ' + name + " - " + msg);
				} else {
					util.debug('TRACE: ' + name + " - " + util.inspect(msg));
				}
			}
		};

		this.debug = function(msg) {
			setLevel();
			if (debug) {
				if (typeof (msg) == "string") {
					util.debug(name + " - " + msg);
				} else {
					util.debug(name + " - " + util.inspect(msg));
				}
			}
		};

		this.info = function(msg) {
			setLevel();
			if (info) {
				if (typeof msg == 'string') {
					util.debug('INFO: ' + name + ' - ' + msg);
				} else {
					util.debug('INFO: ' + name + ' - ' + util.inspect(msg));
				}
			}
		};

		this.warn = function(msg) {
			if (typeof msg == 'string') {
				util.debug('WARN: ' + name + ' - ' + msg);
			} else {
				util.debug('WARN: ' + name + ' - ' + util.inspect(msg));
			}
		};

		this.error = function(msg) {
			if (typeof msg == 'string') {
				util.debug('ERROR: ' + name + ' - ' + msg);
			} else {
				util.debug('ERROR: ' + name + ' - ' + util.inspect(msg));
			}
		};

	}

	var User = (function() {
		var Fifo = context.Fifo;
		var logger = new Logger('User');
		var stdin = process.openStdin();
		var me = -1;
		var you = null;
		var input = new Array();
		var running = true;

		stdin.on('data', function(data) {
			if (running) {
				logger.debug('data on stdin');
				input.push(data);
				you.send([ me ]);
				you.flush();
			} else {
				process.exit(0);
			}
		});

		stdin.on('end', function() {
			logger.debug('EOF on stdin');
			input.push(null);
			you.write([ me ]);
			you.flush();
		});

		// User subordinate
		function result(me2, you2, redirect) {
			me = me2;
			you = you2;
			var self = this;
			self.self = self;
			self.unstable = false;

			self.name = 'User';

			self.stdout = new Fifo();
			self.stdout.open(16);
			self.stdin = new Fifo();
			self.stdin.open(16);

			continuation();

			function continuation() {
				self.stdin
						.wait(function(n) {
							self.stdin
									.read(
											n,
											function(msgv) {
												for ( var i = 0; i < msgv.length; ++i) {
													if (typeof msgv[i] == 'number') {
														switch (msgv[i]) {
														case -1:
															process.stderr
																	.write('Press <RETURN> to exit...');
															running = false;
															break;
														default:
															process.stderr
																	.write('EOF on '
																			+ msgv[i]);
															process.exit(1);
															break;
														}
													} else {
														var msg = assign(
																msgv[i],
																[ 'index',
																		'data' ]);
														assert(typeof msg.index == 'number');
														switch (msg.index) {
														case 1:
															if (typeof msg.data == 'string') {
																process.stdout
																		.write(msg.data);
															} else {
																process.stdout
																		.write(util
																				.inspect(msg.data)
																				+ '\n');
															}
															break;
														case 2:
															if (typeof msg.data == 'string') {
																process.stderr
																		.write(msg.data);
															} else {
																process.stderr
																		.write(util
																				.inspect(msg.data)
																				+ '\n');
															}
															break;
														default:
															assert(false);
															break;
														}
													}
												}
												if (input.length == 0) {
													self.stdout.flush();
												} else {
													var response = input;
													input = [];
													self.stdout
															.send(cons(
																	response,
																	function(
																			msg) {
																		return [
																				0,
																				msg ];
																	}));
													self.stdout.flush();
												}
												continuation();
											});
						});
			}

		}

		result.prototype.initialize = function() {

		};

		return result;
	})();

	var System = (function() {
		include('lib/setl.js', context);
		include('lib/composix.js', context);

		var Nodejs = (function() {
			var logger = new Logger('Nodejs');

			function encode(data, offset, encoding) {
				switch (encoding) {
				case 'us-ascii':
					return data.toString('ascii', offset);
				case 'utf-8':
					return data.toString('utf8', offset);
				case 'base64':
					return data.toString('base64', offset);
				default:
					throw new Error("encoding '" + encoding
							+ "' not supported by Nodejs");
				}
			}

			function addMimeField(obj, field) {
				var match = /^([\s:]):([^\r\n\f\v;]+)(;[^\r\n\f\v]+)?\r\n$/
						.exec(field);
				var content = /^[ \t]*([^;\s\/]+)[ \t]*(\/[ \t]*([^;\s\/]+)[ \t]*)*$/
						.exec(match[2]);
				var arguments = /^(;[ \t]*([^\s=]+)=[ \t]*("[^"]*"|[^"\s;]*)[ \t]*)*$/
						.exec(match[3]);
				content.push(arguments);
				obj[match[1]] = content;
			}

			function getMimeFields(header) {
				var match = /^([^\r\n\f\v]+\r\n)+\r\n$/.exec(header);
				assert(match != null);
				var version = /^MIME-Version: 1.0[^\r\n\f\v]*\r\n$/
						.exec(match[1]);
				if (version == null) {
					return null;
				} else {
					var result = {
						'MIME-Version' : '1.0'
					};
					for ( var i = 2; i < match.length; ++i) {
						addMimeField(result, match[i]);
					}
					assert('Content-Type' in result);
					switch (result['Content-Type'].length) {
					case 2:
						result['Content-Type'].push({
							'charset' : 'us-ascii'
						});
						break;
					case 3:
						assert('charset' in result['Content-Type'][2]);
						break;
					default:
						assert(false);
						break;
					}
					return result;
				}
			}

			function NodejsMimeSource(cpx, pid, fd, argv) {
				var encoding = 'utf-8';

				cpx.get(fd[0], function(data) {
					var header = data.toString("ascii");
					var match = /\r\n\r\n(.*)$/.exec(header);
					if (match != null) {
						var headersize = header.length - match[1].length;
						var mime = getMimeFields(header.substr(0, headersize));
						cpx.put(fd[1], mime);
						if (headersize < header.length) {
							cpx.put(fd[1], encode(data, headersize, encoding));
						}
						cpx.flush(fd[1]);
					} else {
						var mime = {
							'MIME-Version' : '1.0',
							'Content-Type' : [ 'text', 'plain', {
								'charset' : encoding
							} ]
						};
						cpx.put(fd[1], mime);
						cpx.put(fd[1], encode(data, 0, encoding));
						cpx.flush(fd[1]);
					}
					continuation();
				});

				function continuation() {
					cpx.get(fd[0], function(data) {
						cpx.put(fd[1], encode(data, 0, encoding));
						cpx.flush(fd[1]);
						continuation();
					});
				}
			}

			// function File(cpx, fd) {
			// cpx.get(fd[0], function(filename) {
			// fs.readFile(filename, 'utf8', function(data) {
			// var mime = {
			// 'MIME-Version' : '1.0',
			// 'Content-Type' : [ 'text', 'plain', {
			// 'charset' : 'utf-8'
			// } ]
			// };
			// cpx.put(fd[1], mime);
			// cpx.put(fd[1], data);
			// cpx.close(fd[1]);
			// });
			// });
			// }

			function Server(cpx, pid, fd) {
				var setl = fs.readFileSync('lib/setl.js', 'utf8');
				continuation();
				function continuation() {
					cpx.get(fd[0],
							function(request) {
								var req = request.shift();
								switch (req) {
								case 'getfile':
									var mime = {
										'MIME-Version' : '1.0',
										'Content-Type' : [ 'text', 'plain', {
											'charset' : 'utf-8'
										} ]
									};
									assert(request[0].charAt(0) == '/');
									try {
										var data = fs.readFileSync(request[0]
												.substr(1), 'utf8');
										cpx.put(fd[1], mime);
										cpx.put(fd[1], data);
										cpx.flush(fd[1]);
									} catch (e) {
										logger.info(e);
										cpx.put(fd[1], {});
										cpx.put(fd[1], {});
										cpx.flush(fd[1]);
									}
									break;
								case 'load':
									var sandbox = {};
									request[1] += 'this.symbol = ' + request[0]
											+ ';\n';
									evals.NodeScript.runInNewContext(setl,
											sandbox, 'setl.js');
									evals.NodeScript.runInNewContext(
											request[1], sandbox, request[0]);
									if (typeof sandbox.symbol == 'function') {
										request[2].load(request[0],
												sandbox.symbol);
										cpx.put(fd[1], true);
										cpx.flush(fd[1]);
									} else {
										cpx.put(fd[1], false);
										cpx.flush(fd[1]);
									}
									break;
								default:
									logger.error('unknown request:');
									logger.error(req);
									logger.error(request);
									break;
								}
								continuation();
								// var fdv = [ cpx.open(), cpx.open() ];
								// cpx.connect(fdv, addrv);
								// cpx.exec('File', fdv);
							});
				}
			}

			function Initializer(cpx) {
				cpx.open(0, 1, 2, 3);
				cpx.exec(NodejsMimeSource, [ 0, 1 ]);
				cpx.exec(Server, [ 3, 2 ]);
			}

			// Nodejs subordinate
			return function(me, you, redirect) {
				this.self = new Subordinate(me, you, redirect, this, logger,
						'Nodejs');

				this.Initializer = Initializer;
			};
		})();

		// System superior
		return function(me, you, redirect) {
			this.self = new Superior(me, you, redirect, new Logger('System'),
					'System', [ [ Nodejs, 1, -4, -5, 6 ],
							[ context.ComPosiX, 4, -2, -3, 5, -6 ] ]);
		};
	})();

	// Root superior
	return function(me, you, redirect) {
		this.self = new Superior(me, you, redirect, new Logger('Root'), 'Root',
				[ [ User, -1, 2, 3 ], [ System, 1, -2, -3 ] ]);
	};
})();

var root = new Root(0, null, []);
root.self.initialize();
